package web_middleware

import (
	"fmt"
	"gfrest/common/ctxkit"
	"gfrest/common/log"
	"gfrest/common/util"
	"gfrest/web/app/dao"
	"gfrest/web/app/service"
	"net/http"
	"time"

	jwt "github.com/gogf/gf-jwt"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/util/gvalid"
)

var (
	// The underlying JWT middleware.
	GfJWTMiddleware *jwt.GfJWTMiddleware
)

type UserLogin struct {
	Username string `json:"username" v:"username     @required"`
	Password string `json:"password" v:"password @required"`
	// IdKeyC   string `json:"idKeyC" v:"idKeyC @required"`
	// IdValueC string `json:"idValueC" v:"idValueC @required"`
}

// Customized login parameter validation rules.
var ValidationRules = util.StructValidTags(UserLogin{})

// Initialization function,
// rewrite this function to customized your own JWT settings.
func init() {
	authMiddleware, err := jwt.New(&jwt.GfJWTMiddleware{
		Realm:           "test zone",
		Key:             []byte("secret key"),
		Timeout:         time.Hour * 5,
		MaxRefresh:      time.Hour * 5,
		IdentityKey:     "id",
		TokenLookup:     "header: Authorization, query: jwt-token, cookie: jwt-token",
		TokenHeadName:   "jwt",
		TimeFunc:        time.Now,
		Authenticator:   Authenticator,
		LoginResponse:   LoginResponse,
		RefreshResponse: RefreshResponse,
		Unauthorized:    Unauthorized,
		IdentityHandler: IdentityHandler,
		PayloadFunc:     PayloadFunc,
		SendCookie:      true,
		Authorizator:    Authorizator,
	})
	if err != nil {
		glog.Errorf("JWT Error:" + err.Error())
	}
	GfJWTMiddleware = authMiddleware
}

// Authorizator Authorizator set user id in request context
func Authorizator(data interface{}, r *ghttp.Request) bool {
	id := data
	user, err := dao.AuthUser.Cache(time.Hour, fmt.Sprintf("auth_user:%v", id)).Where(
		"id=?", id).Where("is_active=", 1).One()
	if err != nil || user == nil {
		g.Log().Info("user", user)

		log.Infof(r.Context(), "try login whith id %v", id)
		log.Error(r.Context(), err)
		return false
	}
	r = ctxkit.WithUser(r, *user)
	r = ctxkit.WithUserID(r, int64(user.Id))
	r = ctxkit.WithUserName(r, user.Username)
	return true
}

// PayloadFunc is a callback function that will be called during login.
// Using this function it is possible to add additional payload data to the webtoken.
// The data is then made available during requests via c.Get("JWT_PAYLOAD").
// Note that the payload is not encrypted.
// The attributes mentioned on jwt.io can't be used as keys for the map.
// Optional, by default no additional data will be set.
func PayloadFunc(data interface{}) jwt.MapClaims {
	claims := jwt.MapClaims{}
	params := data.(map[string]interface{})
	if len(params) > 0 {
		for k, v := range params {
			claims[k] = v
		}
	}
	return claims
}

// IdentityHandler sets the identity for JWT.
func IdentityHandler(r *ghttp.Request) interface{} {
	claims := jwt.ExtractClaims(r)
	g.Log().Info("claims", claims)
	return claims["id"]
}

// Unauthorized is used to define customized Unauthorized callback function.
func Unauthorized(r *ghttp.Request, code int, message string) {
	glog.Warningf("jwt auth error: code: %v, message: %v", code, message)
	r.Cookie.Remove("jwt-token")
	if r.URL.Path == "/login" {
		r.Response.Status = 401
		r.Response.WriteJson(g.Map{
			"code": code,
			"msg":  message,
		})
		r.ExitAll()
		return
	}

	r.Response.Status = 401
	glog.Warningf("need login, url: %v, data: %v", r.Request.URL, r.GetMap())
	r.Response.WriteJson(g.Map{"msg": "need login"})

	r.ExitAll()
}

// LoginResponse is used to define customized login-successful callback function.
func LoginResponse(r *ghttp.Request, code int, token string, expire time.Time) {
	r.Cookie.Set("jwt-token", token)
	claims, _, _ := GfJWTMiddleware.GetClaimsFromJWT(r)
	perms, _ := service.AuthGroupService.GetUserPerm(int(claims["id"].(float64)))

	r.Response.WriteJson(g.Map{
		"code":     http.StatusOK,
		"token":    token,
		"perms":    perms,
		"expire":   expire.Format(time.RFC3339),
		"username": claims["username"],
		"id":       claims["id"],
	})
	r.ExitAll()
}

// RefreshResponse is used to get a new token no matter current token is expired or not.
func RefreshResponse(r *ghttp.Request, code int, token string, expire time.Time) {
	r.Cookie.Set("jwt-token", token)
	claims, _, _ := GfJWTMiddleware.GetClaimsFromJWT(r)
	perms, _ := service.AuthGroupService.GetUserPerm(int(claims["id"].(float64)))

	r.Response.WriteJson(g.Map{
		"code":  http.StatusOK,
		"token": token,
		"perms": perms,

		"expire":   expire.Format(time.RFC3339),
		"username": claims["username"],
		"id":       claims["id"],
	})
	r.ExitAll()
}

// Authenticator is used to validate login parameters.
// It must return user data as user identifier, it will be stored in Claim Array.
// Check error (e) to determine the appropriate error message.
func Authenticator(r *ghttp.Request) (interface{}, error) {
	data := r.GetMap()
	if e := gvalid.CheckMap(data, ValidationRules); e != nil {
		return "", jwt.ErrFailedAuthentication
	}
	// //判断验证码是否正确
	// if !util.VerifyString(data["idKeyC"].(string), data["idValueC"].(string)) {
	// 	return "", errors.New("验证码输入错误")

	// }
	pass, puser := service.AuthUserService.CheckUserPassword(data["username"].(string), data["password"].(string))
	if pass {
		return g.Map{
			"username": data["username"],
			"id":       puser.Id,
		}, nil
	}
	return nil, jwt.ErrFailedAuthentication
}

// MiddlewareAuth MiddlewareAuth
func MiddlewareAuth(r *ghttp.Request) {
	if !g.Config().GetBool("DEBUG") {
		log.Info(r.Context(), "MiddlewareAuth start")
		GfJWTMiddleware.MiddlewareFunc()(r)
		log.Info(r.Context(), "MiddlewareAuth start")
	}
	r.Middleware.Next()
}
